package epam.concurrency.lab2.task2;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Standard Circular Buffer with java.util.concurrent
 * @author Yuriy Miedviediev
 * @version 2.0 Build 14.08.2014
 */
public class CircularBuffer {

    private final Lock lock;
    private final Condition full;
    private final Condition empty;
    private static final Logger LOGGER = LoggerFactory.getLogger(CircularBuffer.class);
    private static final int DEFAULTCAPACITY = 10;

    //capacity of the buffer
    private final int capacity;

    //data holder
    private int[] contents;

    //current indexes for adding and deleting elements
    private int addIndex = 0;
    private int takeIndex = 0;

    //number of elements in the buffer
    private int length = 0;

    public CircularBuffer(int capacity) {
        this.capacity = capacity;
        contents = new int[capacity];

        lock = new ReentrantLock();
        full = lock.newCondition();
        empty = lock.newCondition();
    }

    public CircularBuffer() {
        this(DEFAULTCAPACITY);
    }

    /**
     * Get the contents of the current Buffer
     * @return array of integers
     */
    public int[] getContents() {
        return Arrays.copyOf(this.contents, this.contents.length);
    }

    /**
     * Add new element to the buffer
     * @param element integer to add to the buffer
     */
    public void add(int element) {
        lock.lock();
        try {
            while(this.isFull())  {
                try {
                    full.await();
                } catch (InterruptedException e) {
                    LOGGER.error(e.getMessage());
                }
            }

            this.addIndex = this.addIndex % this.capacity;
            contents[this.addIndex] = element;
            this.addIndex++;
            this.length++;

            if(this.length > this.capacity) {
                this.length = this.capacity;
            }

            empty.signalAll();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Take and delete (move deletion cursor) element from the buffer
     * @return next integer in the queue
     */
    public int poll() {
        lock.lock();
        try {
            while(this.length <= 0) {
                try {
                    empty.await();
                } catch (InterruptedException e) {
                    LOGGER.error(e.getMessage());
                }
            }

            this.takeIndex = this.takeIndex % this.capacity;
            int result = this.contents[takeIndex];
            this.takeIndex++;
            this.length--;

            if(this.length < 0) {
                this.length = 0;
            }

            full.signalAll();
            return result;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Size of the Circular Buffer
     * @return number of elements in the buffer
     */
    public int size() {
        return this.length;
    }

    /**
     * Check if the buffer is full
     * @return true, if buffer is full, false otherwise
     */
    public boolean isFull() {
        return this.length >= this.capacity;
    }
}